package com.hanter.downloader.service;

import android.content.Context;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.hanter.downloader.Downloader;
import com.hanter.downloader.RandomAccessSource;
import com.hanter.downloader.TaskInfo;
import com.hanter.downloader.exception.ResponseException;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * 文件名：OkhttpService <br/>
 * 描述：Okhttp下载实现
 * 创建时间：2022/02/08 14:29
 *
 * @author wangmingshuo
 * @version 1.0
 */
public class OkhttpService implements DownloadService {

    public static final String TAG = OkhttpService.class.getSimpleName();

    // WeakHashMap 代替
    private final static Map<Downloader.Config, OkHttpClient> sClientMap = new WeakHashMap<>();
    @NonNull
    private volatile OkHttpClient okHttpClient;
    private final Downloader.Config config;

    public OkhttpService(@NonNull Context context) {
        this(Downloader.get(context).getConfig());
    }

    public OkhttpService(@NonNull Downloader.Config config) {
        this.config = config;
        synchronized (OkhttpService.class) {
            OkHttpClient client = sClientMap.get(config);
            if (client != null) {
                this.okHttpClient = client;
            } else {
                this.okHttpClient = new OkHttpClient.Builder()
                        .connectTimeout(config.getConnectTimeout(), TimeUnit.MILLISECONDS)
                        .readTimeout(config.getConnectTimeout(), TimeUnit.MILLISECONDS)
                        .writeTimeout(config.getConnectTimeout(), TimeUnit.MILLISECONDS)
                        .build();
                sClientMap.put(config, this.okHttpClient);
            }
        }
    }

    @Override
    public void updateConfig(@NonNull Downloader.Config config) {
        if (config == this.config) return;

        synchronized (OkhttpService.class) {
            OkHttpClient client = sClientMap.get(config);
            if (client != null) {
                this.okHttpClient = client;
            } else {
                this.okHttpClient = new OkHttpClient.Builder()
                        .connectTimeout(config.getConnectTimeout(), TimeUnit.MILLISECONDS)
                        .readTimeout(config.getConnectTimeout(), TimeUnit.MILLISECONDS)
                        .writeTimeout(config.getConnectTimeout(), TimeUnit.MILLISECONDS)
                        .build();
                sClientMap.put(config, this.okHttpClient);
            }
        }
    }

    @Nullable
    @Override
    public TaskInfo getTaskInfo(@NonNull String url) {
        Response res = null;

        try {
            Request request = new Request.Builder()
                    .head()
                    .url(url)
                    .build();

            res = okHttpClient.newCall(request)
                    .execute();

            if (res.code() == 200) {
                ResponseWrapper rw = ResponseWrapper.get(url, res);
                logHttpInfo(rw);
                return TaskInfo.newTaskInfo(rw);
            } else {
                return null;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            if (res != null && res.body() != null) {
                res.close();
            }
        }
    }

    private void logHttpInfo(ResponseWrapper wrapper) {
        Log.d(TAG, "URL: " + wrapper.url);
        Map<String, List<String>> headers = wrapper.getHeaders();
        if (headers != null) {
            for (String s : headers.keySet()) {
                List<String> values = headers.get(s);
                if (values != null) {
                    for (String value : values) {
                        Log.d(TAG, s + ": " + headers.get(s));
                    }
                }
            }
        }
    }

    @Override
    public boolean isContentRangeSupported(@NonNull String url) {
        return false;
    }

    private long download(@NonNull RandomAccessSource resource, @NonNull String url, long start, long end,
                          @Nullable OnProgressIncreaseListener listener) throws IOException, InterruptedException {
        InputStream is = null;
        int count = 0;
        try {
            if (start > 0)
                resource.seek(start);

            final Request.Builder builder = new Request.Builder()
                    .get()
                    .url(url);
            // 添加设置
            String range = HttpUtils.getRangeHeader(start, end);
            if (range != null) {
                builder.addHeader("Range", range);
            }
            final Response res = okHttpClient.newCall(builder.build())
                    .execute();

            final ResponseBody body = res.body();

            // FIXME If-Range 206 30
            if (res.code() != 200 && res.code() != 206) {
                throw new ResponseException(res.code(), body == null ? null : body.string());
            }
            // body 为 null 时
            if (body == null) {
                return 0;
            }

            is = body.byteStream();
            byte[] buffer = new byte[config.getBufferSize()];
            int len = 0;

            if (listener != null) {
                listener.onIncrease(len);
            }

            while ((len = is.read(buffer, 0, buffer.length)) != -1) {
                if (Thread.currentThread().isInterrupted()) {
                    throw new InterruptedException();
                }

                count += len;
                resource.write(buffer, 0, len);

                if (listener != null)
                    listener.onIncrease(len);
            }
        } finally {
            // 关闭 资源
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            try {
                resource.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return count;
    }

    @Override
    public long download(@NonNull File file, @NonNull String url, long start, long end,
                         @Nullable OnProgressIncreaseListener listener) throws IOException, InterruptedException {
        if (!file.exists()) {
            File parent = file.getParentFile();
            if (parent != null) {
                parent.mkdirs();
            }
            file.createNewFile();
        }

        return download(RandomAccessSource.get(file), url, start, end, listener);
    }

    @Override
    public long download(@NonNull FileDescriptor fd, @NonNull String url, long start, long end,
                         @Nullable OnProgressIncreaseListener listener) throws IOException, InterruptedException {
        return download(RandomAccessSource.get(fd), url, start, end, listener);
    }

    @Override
    public long download(@NonNull Context context, @NonNull Uri uri, @NonNull String url, long start,
                         long end, @Nullable OnProgressIncreaseListener listener) throws IOException, InterruptedException {
        try (ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "rw")) {
            if (pfd == null)
                throw new FileNotFoundException(uri.toString());

            return download(pfd.getFileDescriptor(), url, start, end, listener);
        }
    }

    @Override
    public long download(@NonNull File file, @NonNull String url, @Nullable OnProgressIncreaseListener listener) throws IOException, InterruptedException {
        return download(file, url, -1L, -1L, listener);
    }
}
